/*
 * Copyright (c) 2011 Imre Fazekas.
 *  All rights reserved.
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  Neither the name of the Brillien nor the names of its
 *  terms and concepts may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.streamline.services.db.couchdb;

import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.services.json.JsonServices;
import com.vii.streamline.structures.collections.InnerList;
import org.jcouchdb.db.Database;
import org.jcouchdb.db.Options;
import org.jcouchdb.db.ServerImpl;
import org.jcouchdb.document.BaseDocument;
import org.jcouchdb.document.DesignDocument;
import org.jcouchdb.document.ValueRow;
import org.jcouchdb.document.ViewResult;
import org.jcouchdb.exception.UpdateConflictException;
import org.jcouchdb.util.CouchDBUpdater;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class SimpleCouchMediator implements CouchMediator {

    protected ServerImpl server;
    protected Database database;

    protected String serverAddress;
    protected String dbName;
    protected boolean cleanAtStartup = false;
    protected String couchDesignDocumentsPath;

    protected Logger logger;

    {
        logger = LoggerFactory.getLogger(this.getClass());
    }

    public static <T extends SimpleCouchMediator> T newSimpleCouchMediator(InputStream config, Class<T> classReference) throws IOException, StreamLineException {
        T cdbServices = JsonServices.<T>parseJSON(
                com.vii.streamline.services.IOServices.getStreamAsString(config),
                classReference
        );
        cdbServices.timeToRelax();

        return cdbServices;
    }

    @Override
    public ServerImpl getServer() {
        return server;
    }

    public void setServer(ServerImpl server) {
        this.server = server;
    }

    @Override
    public Database getDatabase() {
        return database;
    }

    public void setDatabase(Database database) {
        this.database = database;
    }

    @Override
    public String getServerAddress() {
        return serverAddress;
    }

    @Override
    public void setServerAddress(String serverAddress) {
        this.serverAddress = serverAddress;
    }

    @Override
    public String getDbName() {
        return dbName;
    }

    @Override
    public void setDbName(String dbName) {
        this.dbName = dbName;
    }

    @Override
    public boolean isCleanAtStartup() {
        return cleanAtStartup;
    }

    @Override
    public void setCleanAtStartup(boolean cleanAtStartup) {
        this.cleanAtStartup = cleanAtStartup;
    }

    @Override
    public String getCouchDesignDocumentsPath() {
        return couchDesignDocumentsPath;
    }

    @Override
    public void setCouchDesignDocumentsPath(String couchDesignDocumentsPath) {
        this.couchDesignDocumentsPath = couchDesignDocumentsPath;
    }

    public Logger getLogger() {
        return logger;
    }

    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**
     * *****************************************************************
     * Management services                             *
     * ******************************************************************
     */
    @Override
    public void updateDesignDocuments() {
        logger.debug(" Updating design documents...");

        CouchDBUpdater updater = new CouchDBUpdater();
        updater.setDatabase(database);
        updater.setDesignDocumentDir(new File(couchDesignDocumentsPath));

        try {
            List<DesignDocument> docs = updater.updateDesignDocuments();
            for (DesignDocument dd : docs) {
                logger.debug(" Successful loaded design document: ", dd);
            }
        } catch (IOException e) {
            logger.error("updateDesignDocuments", e);
        }
    }

    @Override
    public boolean timeToRelax() {
        server = new ServerImpl(serverAddress);

        if (cleanAtStartup) {
            logger.debug(" Cleaning CouchDB...");
            try {
                server.deleteDatabase(dbName);
            } catch (Exception e) {
                logger.debug(" Unsuccessful DB cleaning...");
            }
        }

        logger.debug(" Creating Database:" + dbName);
        server.createDatabase(dbName);

        database = new Database(server, dbName);

        updateDesignDocuments();

        System.out.println("Relaxed.");
        logger.debug("Relaxed.");

        return true;
    }

    @Override
    public void checkDB() throws StreamLineException {
        if (database == null)
            throw new StreamLineException("No couch found to repose!");
    }

    @Override
    public boolean closeCouch() {
        if (database == null)
            return false;

        server.shutDown();

        return true;
    }

    @Override
    public boolean storeDocument(BaseDocument db) throws StreamLineException {
        try {
            database.createOrUpdateDocument(db);
        } catch (UpdateConflictException ue) {
            throw new StreamLineException(StreamLineException.ERROR_SERVER_DEMAND, "Operation need to re-initiated.");
        }

        return true;
    }

    @Override
    public boolean updateDocument(BaseDocument db) throws StreamLineException {
        try {
            database.updateDocument(db);
        } catch (UpdateConflictException ue) {
            throw new StreamLineException(StreamLineException.ERROR_SERVER_DEMAND, "Operation need to re-initiated.");
        }

        return true;
    }

    @Override
    public <T> List<T> getData(Class<T> type, String function) {
        return this.<T>getData(type, function, null);
    }

    @Override
    public <T> List<T> getData(Class<T> type, String function, Options options) {
        List<T> data = new InnerList<T>();
        ViewResult<T> view = database.queryView(
                dbName + function, type, options, null
        );

        for (ValueRow<T> vr : view.getRows()) {
            data.add(vr.getValue());
        }

        return data;
    }

    @Override
    public <T extends BaseDocument> T getDocumentById(Class<T> classReference, String id) throws StreamLineException {
        logger.debug(" GetDocumentById", classReference, id);

        checkDB();

        try {
            return database.getDocument(classReference, id);
        } catch (Exception e) {
            logger.error("Document not found.", e);
            throw new StreamLineException(e.getMessage());
        }
    }

    @Override
    public String toString() {
        return "SimpleCouchMediator{" +
                "server=" + server +
                ", database=" + database +
                ", serverAddress='" + serverAddress + '\'' +
                ", dbName='" + dbName + '\'' +
                ", cleanAtStartup=" + cleanAtStartup +
                ", couchDesignDocumentsPath='" + couchDesignDocumentsPath + '\'' +
                '}';
    }
}
